package com.xingyun.kingdee.erp.tools.param.model;


import com.xingyun.kingdee.erp.common.model.annotation.ErpQueryBillField;
import com.xingyun.kingdee.erp.tools.common.Reflection;
import com.xingyun.kingdee.erp.tools.common.XyFunction;
import lombok.Getter;
import lombok.SneakyThrows;
import org.apache.logging.log4j.util.Strings;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author lingo
 * @version v1.0
 * @date 2020/4/29 15:16
 * @Company www.xyb2b.com
 */
@Getter
public class XyQueryWrapper<T> implements Serializable {
    private static final long serialVersionUID = -8794343427904951836L;

    private List<String> fieldNameList = new ArrayList<>();

    private List<FilterInfo> infoList = new ArrayList<>();

    private Class<T> entityClass;

    private boolean preIsOr = false;


    private XyQueryWrapper() {
    }

    public static <T> XyQueryWrapper<T> of(Class<T> cls) {
        XyQueryWrapper<T> queryWrapper = new XyQueryWrapper<>();
        queryWrapper.entityClass = cls;
        return queryWrapper;
    }

    public XyQueryWrapper<T> select(XyFunction<T, ?>... functionList) {
        for (XyFunction<T, ?> function : functionList) {
            String methodToFieldName = Reflection.getMethodToFieldName(function);
            if (!fieldNameList.contains(methodToFieldName)) {
                fieldNameList.add(methodToFieldName);
            }
        }
        return this;
    }


    public XyQueryWrapper<T> or() {
        preIsOr = true;
        return this;
    }

    public XyQueryWrapper<T> and(XyFunction<XyQueryWrapper<T>, XyQueryWrapper<T>> function) {
        XyQueryWrapper<T> nesedInfo = function.apply(new XyQueryWrapper<>());
        if (!CollectionUtils.isEmpty(infoList)) {
            this.addToList(FilterInfo.getNestedOr(false, nesedInfo.getInfoList()));
        }
        return this;
    }


    public XyQueryWrapper<T> or(XyFunction<XyQueryWrapper<T>, XyQueryWrapper<T>> function) {
        XyQueryWrapper<T> nesedInfo = function.apply(new XyQueryWrapper<>());
        if (!CollectionUtils.isEmpty(infoList)) {
            this.addToList(FilterInfo.getNestedOr(true, nesedInfo.getInfoList()));
        }
        return this;
    }

    public XyQueryWrapper<T> eq(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.EQUAL;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> like(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.LIKE;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }


    public XyQueryWrapper<T> notLike(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.NOT_LIKE;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> likeLeft(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.LIKE;
        this.addFilterInfo(getter, filterType, "%" + value);
        return this;
    }

    public XyQueryWrapper<T> likeRight(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.LIKE;
        this.addFilterInfo(getter, filterType, value + "%");
        return this;
    }

    public XyQueryWrapper<T> ne(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.NOT_EQUAL;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> lt(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.LESS_THAN;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> le(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.LESS_OR_EQUAL;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> gt(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.GREATER_THAN;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> ge(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.GREATER_OR_EQUAL;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> in(XyFunction<T, ?> getter, Object... value) {
        FilterType filterType = FilterType.IN;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }


    public XyQueryWrapper<T> notIn(XyFunction<T, ?> getter, Object value) {
        FilterType filterType = FilterType.NOT_IN;
        this.addFilterInfo(getter, filterType, value);
        return this;
    }

    public XyQueryWrapper<T> isNull(XyFunction<T, ?> getter) {
        FilterType filterType = FilterType.IS_NULL;
        this.addFilterInfo(getter, filterType);
        return this;
    }

    public XyQueryWrapper<T> isNotNull(XyFunction<T, ?> getter) {
        FilterType filterType = FilterType.IS_NOT_NULL;
        this.addFilterInfo(getter, filterType);
        return this;
    }


    private void addFilterInfo(XyFunction<T, ?> getter, FilterType filterType, Object... value) {
        FilterInfo filterInfo = new FilterInfo(getter, preIsOr, filterType, value);
        this.addToList(filterInfo);
    }

    private void addToList(FilterInfo filterInfo) {
        // 重置
        preIsOr = false;
        infoList.add(filterInfo);
    }

    public List<FilterInfo> getInfoList() {
        return infoList;
    }

    public String buildWhereSql() {
        List<FilterInfo> infoList = this.infoList;
        return infoListToSql(infoList);
    }

    private String infoListToSql(List<FilterInfo> infoList) {
        if (Objects.isNull(infoList) || infoList.isEmpty()) {
            return Strings.EMPTY;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < infoList.size(); i++) {
            FilterInfo info = infoList.get(i);
            boolean isNeedLinkPre = i != 0;
            if (isNeedLinkPre) {
                //与前面隔开
                builder.append(" ");
            }
            builder.append(this.getSqlString(info, isNeedLinkPre));
        }
        return builder.toString();
    }

    private String getSqlString(FilterInfo filterInfo, boolean isNeedLinkPre) {
        String prefix = Strings.EMPTY;
        if (isNeedLinkPre) {
            prefix = filterInfo.getOpt().code + " ";
        }
        if (filterInfo.isNested()) {
            return String.format("%s(%s)", prefix, infoListToSql(filterInfo.getNestedInfo()));
        }
        String fieldName = filterInfo.getFieldName();
        String valueStr = getValueSqlString(filterInfo.getValues());
        return String.format("%s" + filterInfo.getFilterType().getKeyword(), prefix, getQueryBillFieldKeyByName(fieldName), valueStr);
    }



    @SneakyThrows
    private String getQueryBillFieldKeyByName(String fieldName) {
        Field declaredField = this.entityClass.getDeclaredField(fieldName);
        ErpQueryBillField annotation = declaredField.getAnnotation(ErpQueryBillField.class);
        if (!Objects.isNull(annotation) && !StringUtils.isEmpty(annotation.fieldKey())) {
            return annotation.fieldKey();
        }
        return fieldName;
    }

    private static String getValueSqlString(Object value) {
        if (value instanceof Boolean) {
            return ((Boolean) value) ? "1" : "0";
        }
        if (value instanceof Number) {
            return String.valueOf(value.toString());
        }
        if (value.getClass().isArray()) {
            //数组类型
            Object[] value1 = (Object[]) value;
            List<String> valueStrList = new ArrayList<>();
            Arrays.asList(value1).forEach(v -> valueStrList.add(getValueSqlString(v)));
            return Strings.join(valueStrList, ',');
        }
        if (value instanceof Iterable) {
            List<String> valueStrList = new ArrayList<>();
            ((Iterable) value).forEach(v -> valueStrList.add(getValueSqlString(v)));
            return Strings.join(valueStrList, ',');
        }
        return String.format("'%s'", value);
    }

    public String getQueryBillFieldKeys() {
        if (Objects.isNull(fieldNameList) || fieldNameList.isEmpty()){
            return Strings.EMPTY;
        }
        return fieldNameList.stream().map(this::getQueryBillFieldKeyByName).collect(Collectors.joining(","));
    }
}
